﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SharpSoft.WPF.Controls
{
    /// <summary>
    /// 粒子控件,使用WriteableBitmapEx库实现
    /// </summary>
    public partial class ParticleControl : UserControl
    {
        public ParticleControl()
        {
            InitializeComponent();
            this.Loaded += ParticleControl_Loaded;
            this.Unloaded += ParticleControl_Unloaded;

            particleEmitter = new ParticleEmitter();
            this.particleEmitter.TargetBitmap = this.Bitmap;
            this.ParticleBitmap = CreateParticleBitmap_Circle(50, 50);
            //20191125直接在依赖属性内初始化该值会导致返回的WriteableBitmap是冻结状态，暂不清楚原因。 
            this.particleEmitter.ParticleBitmap = this.ParticleBitmap;
            this.particleEmitter.updateInterval = this.UpdateInterval;

            CompositionTarget.Rendering += CompositionTarget_Rendering;
        }

        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            var secs = stopwatch.Elapsed.TotalSeconds;
            using (this.Bitmap.GetBitmapContext())
            {
                this.Bitmap.Clear(Colors.Transparent);
                this.particleEmitter.Update(secs);
            }
        }

        private void ParticleControl_Unloaded(object sender, RoutedEventArgs e)
        {
            stopwatch.Stop();

        }

        private void ParticleControl_Loaded(object sender, RoutedEventArgs e)
        {
            stopwatch.Start();

        }


        #region 私有字段
        private System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
        //private System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
        private ParticleEmitter particleEmitter;

        #endregion
        /// <summary>
        /// 当前位图
        /// </summary>
        public WriteableBitmap Bitmap
        {
            get { return (WriteableBitmap)GetValue(BitmapProperty); }
            private set { SetValue(BitmapProperty, value); }
        }

        public static readonly DependencyProperty BitmapProperty =
            DependencyProperty.Register("Bitmap", typeof(WriteableBitmap), typeof(ParticleControl), new PropertyMetadata(null));


        /// <summary>
        /// 用于显示粒子的位图
        /// </summary>
        public WriteableBitmap ParticleBitmap
        {
            get { return (WriteableBitmap)GetValue(ParticleBitmapProperty); }
            set { SetValue(ParticleBitmapProperty, value); }
        }

        public static readonly DependencyProperty ParticleBitmapProperty =
            DependencyProperty.Register("ParticleBitmap", typeof(WriteableBitmap), typeof(ParticleControl), new PropertyMetadata(null));


        /// <summary>
        /// 出现粒子的中心，为null表示不产生粒子。
        /// </summary>
        public Point? Center
        {
            get { return (Point?)GetValue(CenterProperty); }
            set { SetValue(CenterProperty, value); }
        }

        public static readonly DependencyProperty CenterProperty =
            DependencyProperty.Register("Center", typeof(Point?), typeof(ParticleControl), new PropertyMetadata(null));


        /// <summary>
        /// 更新粒子的时间间隔（单位：秒），默认为0.005秒
        /// </summary>
        public double UpdateInterval
        {
            get { return (double)GetValue(UpdateIntervalProperty); }
            set { SetValue(UpdateIntervalProperty, value); }
        }

        public static readonly DependencyProperty UpdateIntervalProperty =
            DependencyProperty.Register("UpdateInterval", typeof(double), typeof(ParticleControl), new PropertyMetadata(0.003d));

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (e.Property == CenterProperty)
            {
                this.particleEmitter.Center = this.Center;
            }
            else if (e.Property == BitmapProperty)
            {
                this.particleEmitter.TargetBitmap = this.Bitmap;
            }
            else if (e.Property == ParticleBitmapProperty)
            {
                this.particleEmitter.ParticleBitmap = this.ParticleBitmap;
            }
            else if (e.Property == UpdateIntervalProperty)
            {
                this.particleEmitter.updateInterval = this.UpdateInterval;
            }
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);
            var w = (int)this.ActualWidth;
            var h = (int)this.ActualHeight;
            this.Bitmap = BitmapFactory.New(w, h);
            using (this.Bitmap.GetBitmapContext())
            {
                this.Bitmap.Clear(Colors.Transparent);
            }

        }

        #region 创建常见粒子基元图像的方法


        public static WriteableBitmap CreateParticleBitmap_Circle(int width, int height)
        {
            var wb = BitmapFactory.New(width, height);
            var xc = width / 2;
            var yc = height / 2;
            using (wb.GetBitmapContext())
            {
                wb.Clear(Colors.Transparent);
                //wb.FillEllipseCentered(xc, yc, xc, yc, Color.FromArgb(122, 255, 255, 255));
                wb.FillEllipseCentered(xc, yc, xc / 2, yc / 2, Colors.White);
            }
            return wb;

        }
        #endregion

        #region InnerClass
        /// <summary>
        /// 粒子发射器
        /// </summary>
        public class ParticleEmitter
        {
            #region Fields
            /// <summary>
            /// 产生粒子的中心位置
            /// </summary>
            public Point? Center { get; set; }
            public List<Particle> Particles = new List<Particle>();
            Random rand = new Random();
            public WriteableBitmap TargetBitmap;
            public WriteableBitmap ParticleBitmap;
            double elapsedRemainder;
            double lastElapsed = 0;
            public double updateInterval = 0.005;
            /// <summary>
            /// 粒子颜色
            /// </summary>
            public Color ParticleColor { get; set; } = Colors.Blue;
            #endregion

            #region Contructors

            public ParticleEmitter()
            {
            }

            #endregion

            #region Methods

            void CreateParticle()
            {
                if (!Center.HasValue)
                {//Center没有值时不在创建新的粒子
                    return;
                }
                Particle p = new Particle();
                double speed = rand.Next(20) + 140;
                double angle = Math.PI * 2 * rand.Next(10000) / 10000;
                p.Velocity.X = Math.Sin(angle) * speed;
                p.Velocity.Y = Math.Cos(angle) * speed;
                p.Position = new Point(Center.Value.X - 16, Center.Value.Y - 16);
                p.Color = ParticleColor;
                p.Lifespan = 0.5 + rand.Next(200) / 1000d;
                p.Initiailize();
                Particles.Add(p);
            }

            public void Update(double elapsedSeconds)
            {
                elapsedRemainder = elapsedSeconds - lastElapsed;
                lastElapsed = elapsedSeconds;
                while (elapsedRemainder > updateInterval)
                {
                    elapsedRemainder -= updateInterval;
                    CreateParticle();
                    for (int i = Particles.Count - 1; i >= 0; i--)
                    {
                        Particle p = Particles[i];
                        p.Update(updateInterval);
                        if (p.Color.A == 0) Particles.Remove(p);//完全透明时移除粒子
                    }
                }
                if (TargetBitmap == null || ParticleBitmap == null)
                {
                    return;
                }
                using (TargetBitmap.GetBitmapContext())
                {
                    using (ParticleBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
                    {
                        for (int i = 0; i < Particles.Count; i++)
                        {
                            Particle p = Particles[i];
                            TargetBitmap.Blit(p.Position, ParticleBitmap, new Rect(0, 0, ParticleBitmap.Width, ParticleBitmap.Height), p.ActualColor, WriteableBitmapExtensions.BlendMode.Alpha);
                        }
                    }
                }
            }
            #endregion
        }
        /// <summary>
        /// 粒子
        /// </summary>
        public class Particle
        {
            #region Fields

            public Point Position;
            public Point Velocity;
            public Color Color;

            private Color _ActualColor;//颜色减淡的比例
            public Color ActualColor
            {
                get
                {
                    return this._ActualColor;
                }
            }
            /// <summary>
            /// 粒子的生存周期
            /// </summary>
            public double Lifespan;
            public double Elapsed;

            #endregion

            #region Methods

            public void Initiailize()
            {
                Elapsed = 0;
            }

            public void Update(double elapsedSeconds)
            {
                Elapsed += elapsedSeconds;
                if (Elapsed > Lifespan)
                {
                    Color.A = 0;
                    return;
                }
                var colorShoalRate = Math.Max((1 - Elapsed / Lifespan), 0.00001d);
                if (colorShoalRate <= 0)
                {
                    Color.A = 0;
                    return;
                }
                if (colorShoalRate < 0.3)
                {
                    colorShoalRate = 0.3d;
                }
                this._ActualColor = Color.Shoal(colorShoalRate);
                Position.X += Velocity.X * elapsedSeconds;
                Position.Y += Velocity.Y * elapsedSeconds;
            }

            #endregion
        }

        #endregion
    }
}
